﻿//
// XN4SL - Xna Framework for Silverlight.
//
using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using Xn4Sl.Framework.Graphics;
using Xn4Sl.Framework.Media;
using Xn4Sl.Framework.Audio;

namespace Xn4Sl.Framework.Content
{
    /// <summary>
    /// The ContentManager is the run-time component which loads managed objects
    /// from the binary files.  It also manages the lifespan of the loaded objects, 
    /// disposing the content manager will also dispose any assets which are themselves 
    /// System.IDisposable.
    /// </summary>
    public class ContentManager : IDisposable
    {
        /// <summary>
        /// List of loaded assets.
        /// </summary>
        private Dictionary<string, object> assets = new Dictionary<string, object>();

        /// <summary>
        /// List of free assets.
        /// </summary>
        private List<IDisposable> clonedAssets = new List<IDisposable>();

        /// Initializes a new instance of ContentManager. 
        /// </summary>
        /// <param name="serviceProvider">The service provider that the ContentManager should use to locate services.</param>
        public ContentManager(IServiceProvider serviceProvider) 
        {
            this.serviceProvider = serviceProvider;
        }
        
        /// <summary>
        /// Initializes a new instance of ContentManager.
        /// </summary>
        /// <param name="serviceProvider">The service provider the ContentManager should use to locate services.</param>
        /// <param name="rootDirectory">The root directory to search for content.</param>
        public ContentManager(IServiceProvider serviceProvider, string rootDirectory) 
        {
            this.serviceProvider = serviceProvider;
            this.rootDirectory = rootDirectory;
        }
        
        /// <summary>
        /// Gets or sets the root directory associated with this ContentManager.
        /// </summary>
        private string rootDirectory = "";
        public string RootDirectory 
        {
            get
            {
                return this.rootDirectory;
            }
            set
            {
                this.rootDirectory = value;
            }
        }
        
        /// <summary>
        /// Gets the service provider associated with the ContentManager.
        /// </summary>
        private IServiceProvider serviceProvider;
        public IServiceProvider ServiceProvider
        {
            get
            {
                return this.serviceProvider;
            }
        }
        
        /// <summary>
        /// Releases all resources used by the ContentManager class.
        /// </summary>
        public void Dispose() { }
        
        /// <summary>
        /// Releases the unmanaged resources used by the ContentManager and optionally
        /// releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; 
        /// false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing) { }

        /// <summary>
        /// Loads an asset.
        /// </summary>
        /// <typeparam name="T">Asset name, relative to the loader root directory.</typeparam>
        /// <param name="assetName"></param>
        /// <returns>The loaded asset. Repeated calls to load the same asset will 
        /// return the same object instance.</returns>
        public virtual T Load<T>(string assetName) 
        {
            bool fresh = false;

            if (!this.assets.ContainsKey(assetName))
            {
                //
                // Read an asset if not exist.
                //
                this.assets.Add(assetName, this.ReadAsset<T>(assetName, null));

                fresh = true;
            }

            T asset = (T)this.assets[assetName];

            if (!fresh && typeof(T) == typeof(Texture2D))
            {
                //
                // Return a copy of the asset.
                //
                Texture2D texture = asset as Texture2D;
                this.clonedAssets.Add(texture);
                return (T)(object)texture.Clone();
            }
            else
            {
                return asset;
            }
        }
        /// <summary>
        /// Opens a stream for reading the specified asset. Derived classes can replace
        /// this to implement pack files or asset compression.
        /// </summary>
        /// <param name="assetName">The name of the asset being read.</param>
        /// <returns>The opened stream.</returns>
        protected virtual Stream OpenStream(string assetName)
        {
            //
            // Find an asset in a XAP package.
            //
            try
            {
                XmlXapResolver xmlRes = new XmlXapResolver();
                return xmlRes.GetEntity(new Uri(assetName, UriKind.Relative), null, null) as Stream;
            }
            catch
            {
                throw new Exception(String.Format("Failed to load: {0}!", assetName));
            }
        }

        /// <summary>
        /// Returns true if file exist in application XAP package.
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public bool FileExist(string filename)
        {
            try
            {
                return this.OpenStream(filename) != null;
            }
            catch { }

            return false;
        }

        /// <summary>
        /// Low-level worker method that reads asset data.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="assetName">The name of the asset to be loaded from disk.</param>
        /// <param name="recordDisposableObject">Unsupported.</param>
        /// <returns>Returns the loaded asset.</returns>
        protected T ReadAsset<T>(string assetName, Action<IDisposable> recordDisposableObject)
        {
            //
            // TODO: Add support for the pipeline format...?
            //
            if (typeof(T) == typeof(SpriteFont))
            {
                string assetFileName = String.Concat(this.RootDirectory, "/", assetName, ".spritefont");
                Stream stream = this.OpenStream(assetFileName);
                return (T)(object)(SpriteFont.FromFile(stream));
            }
            else if (typeof(T) == typeof(Texture2D))
            {
                string assetFileName = String.Concat(this.RootDirectory, "/", assetName, ".png");
                GraphicsDeviceManager gdm = this.serviceProvider.GetService(typeof(GraphicsDeviceManager)) as GraphicsDeviceManager;
                Stream stream = this.OpenStream(assetFileName);
                return (T)(object)(Texture2D.FromFile(gdm.GraphicsDevice, stream));
            }
            else if (typeof(T) == typeof(Song))
            {
                string assetFileName = String.Concat(this.RootDirectory, "/", assetName, ".wma");
                return (T)(object)(new Song(assetFileName));
            }
            else if (typeof(T) == typeof(SoundEffect))
            {
                string assetFileName = String.Concat(this.RootDirectory, "/", assetName, ".wma");
                return (T)(object)(new SoundEffect(){ Name = assetFileName });
            }

            return (T)(object)null;
        }

        /// <summary>
        /// Disposes all data that was loaded by this ContentManager.
        /// </summary>
        public virtual void Unload()
        {
            foreach (IDisposable item in this.assets.Values)
            {
                item.Dispose();
            }
            this.assets.Clear();

            foreach (IDisposable item in this.clonedAssets)
            {
                item.Dispose();
            }
            this.clonedAssets.Clear();
        }
    }
}

